﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Xml;
using MugenInjection.Core.Components;
using MugenInjection.Infrastructure;
using MugenInjection.Interface;
using MugenInjection.Interface.Components;
using MugenInjection.Xml.Infrastructure;
using MugenInjection.Xml.Interface;
using MugenInjection.Xml.Interface.Components;

namespace MugenInjection.Xml.Components
{
    /// <summary>
    /// Represents the component to add support load bindings from xml.
    /// </summary>
    public class XmlBindingLoaderComponent : Component, IXmlBindingLoaderComponent
    {
        #region Fields

        private IXmlBindingReader _xmlBindingReader;
        private readonly CacheCollection<string, List<IBinding>> _loadedBinding;
        private readonly List<string> _loadedModules;

        #endregion

        #region Constructor

        /// <summary>
        /// Initializes a new instance of the <see cref="XmlBindingLoaderComponent"/> class.
        /// </summary>
        public XmlBindingLoaderComponent()
        {
            _loadedBinding = new CacheCollection<string, List<IBinding>>();
            _loadedModules = new List<string>();
            Modules = new ReadOnlyCollection<string>(_loadedModules);
            XmlBindingReader = new XmlBindingReader();
        }

        #endregion

        #region Overrides of Component

        /// <summary>
        /// Indicates whether the component is unique.
        /// </summary>
        public override sealed bool IsSingle
        {
            get { return true; }
        }

        /// <summary>
        /// Gets the component type. Need to find a component in the container.
        /// </summary>
        public override Type ComponentType
        {
            get { return typeof(IXmlBindingLoaderComponent); }
        }

        /// <summary>
        /// Creates a new object that is a copy of the current instance.
        /// </summary>
        /// <returns>
        /// A new object that is a copy of this instance.
        /// </returns>
        public override IComponent Clone()
        {
            return new XmlBindingLoaderComponent();
        }

        #endregion

        #region Implementation of IXmlBindingBuilderComponent

        /// <summary>
        /// Gets the loaded modules.
        /// </summary>
        public ReadOnlyCollection<string> Modules { get; private set; }

        /// <summary>
        /// Gets or sets the <see cref="IXmlBindingReader"/> for read bindings.
        /// </summary>
        public IXmlBindingReader XmlBindingReader
        {
            get { return _xmlBindingReader; }
            set
            {
                Validate.PropertyNotNull(value, "XmlBindingReader");
                _xmlBindingReader = value;
            }
        }

        /// <summary>
        /// Loads a series instances of <see cref="IBinding"/> from xml.
        /// </summary>
        /// <param name="xml">The specified xml for load bindings.</param>
        /// <param name="moduleName">The specified module name for load, default used from xml.</param>
        public void LoadBindings(string xml, string moduleName = null)
        {
            Validate.ArgumentNotNullOrEmpty(xml, "xml");
            XmlLoaderUtils.VerifyXmlFile(xml);
            string xmlModuleName;
            var xmlBindingBuilders = XmlBindingReader.ReadBindings(GetBindingsElement(xml), out xmlModuleName);
            if (moduleName == null)
                moduleName = xmlModuleName;
            if (string.IsNullOrEmpty(moduleName))
                throw new ArgumentException("Module name cannot be a null.");
            if (xmlBindingBuilders.Count == 0)
                throw new ArgumentException("Bindings cannot be empty.");
            lock (_loadedBinding)
            {
                if (_loadedBinding[moduleName] != null)
                    throw new ArgumentException(string.Format("Xml module with name {0} all ready loaded.", moduleName));
                var listBindings = new List<IBinding>();
                foreach (var xmlBindingBuilder in xmlBindingBuilders)
                    listBindings.Add(xmlBindingBuilder.Build(Injector));
                _loadedBinding[moduleName] = listBindings;
                _loadedModules.Add(moduleName);
            }
        }

        /// <summary>
        /// Unloads a bindings from the specified xml module.
        /// </summary>
        /// <param name="moduleName">The specified xml module.</param>
        public void UnloadBindings(string moduleName)
        {
            Validate.ArgumentNotNull(moduleName, "moduleName");
            lock (_loadedBinding)
            {
                var bindings = _loadedBinding[moduleName];
                if (bindings == null)
                    throw new ArgumentException();
                foreach (var binding in bindings)
                    Injector.Bindings.RemoveBinding(binding);
                _loadedBinding.Remove(moduleName);
                _loadedModules.Add(moduleName);
            }
        }

        #endregion

        #region Methods

        protected virtual IXmlElement GetBindingsElement(string xml)
        {
#if SILVERLIGHT
            var xElement = System.Xml.Linq.XDocument.Parse(xml);
            return new XElementAdapter(xElement.Element(System.Xml.Linq.XName.Get(XmlLoaderUtils.BindingsElementName, XmlLoaderUtils.BindingsNamespace)));
#else
#if NETFX_CORE
            var xElement = System.Xml.Linq.XDocument.Parse(xml);
            return new XElementAdapter(xElement.Element(System.Xml.Linq.XName.Get(XmlLoaderUtils.BindingsElementName, XmlLoaderUtils.BindingsNamespace)));
#else
            var rootElement = new XmlDocument();
            rootElement.LoadXml(xml);
            var xmlElement = (XmlElement)rootElement.GetElementsByTagName(XmlLoaderUtils.BindingsElementName, XmlLoaderUtils.BindingsNamespace)[0];
            return new XmlElementAdapter(xmlElement);
#endif
#endif
        }

        #endregion

    }
}